package com.hornsun.authorization;

import com.hornsun.config.ConfigInfo;
import com.hornsun.config.IRedisService;
import com.hornsun.data.constant.types.DataPackType;
import com.hornsun.data.dbo.*;
import com.hornsun.data.repository.ActivitiesRepository;
import com.hornsun.data.repository.AuthorizationRepository;
import com.hornsun.data.repository.ClientRepository;
import com.hornsun.data.repository.RoleRepository;
import com.hornsun.util.DESUtil;
import com.hornsun.util.RSAUtils;
import com.hornsun.util.SerializeUtils;
import com.hornsun.util.StrUtil;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Date;
import java.util.List;

/**
 * Created by 编译中... on 2018/6/11.
 */
// 线程执行任务类
@Service
public class AsyncTaskService {
    @Autowired
    private ConfigInfo configInfo;

    @Autowired
    private ClientRepository clientRepository ;

    @Autowired
    private IRedisService service;

    @Autowired
    private AuthorizationRepository authorizationRepository;

    @Autowired
    private ActivitiesRepository activitiesRepository;

    @Autowired
    private RoleRepository roleRepository;


    @Async
    public void heartBeatHandler(){
        try {
            DatagramSocket socket =new DatagramSocket(configInfo.getPort());
            while (true) {
                byte[] buffer = new byte[256];
                DatagramPacket packet = new DatagramPacket(buffer, buffer.length);
                socket.receive(packet);
                messageHandler(socket,packet);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Async
    public void messageHandler(DatagramSocket socket, DatagramPacket packet) {
        byte[] backData = null;
        int nLen = packet.getLength();
        byte[] rsaBackData = null;
        try {
            DataPackType packType = DataPackType.dataPackTypeByType(nLen);
            if (packType!=null){
                switch (packType) {
                    case dptLoginData:
                        // 处理登录的请求
                        LoginData loginData = LoginData.fromBytesArray(packet.getData());
                        LicenseData licenseData = new LicenseData();
                        //如果参数为空，直接返回token:0
                        if (loginData==null){
                            licenseData.token = 0;
                            backData = LicenseData.toBytesArray(licenseData);
                             rsaBackData = encryptBackData(backData);
                            break;
                        }
                        String account = new String(StrUtil.arrayTrim(loginData.name));
                        String password = Base64.encodeBase64String(StrUtil.arrayTrim(loginData.encyptPwd));
                        String mac = StrUtil.macToString(loginData.mac);
                        String ipUrl = packet.getAddress().toString()+":"+packet.getPort();
                        licenseData.token = loginData.token;
                        Client client = clientRepository.getByAccount(account);

                        //账号密码是否正确
                        if (!clientRepository.checkAccountInfo(account,password)){
                            licenseData.code = PacketData.HeartBeatCode.hbcUserError;
                            backData = LicenseData.toBytesArray(licenseData);
                             rsaBackData = encryptBackData(backData);
                            break;
                        }

                        //判断是否是初始密码
                        if(DESUtil.encrypt(configInfo.getInitialPassword()).equals(password)){
                            licenseData.code = PacketData.HeartBeatCode.hbcUnitialPwd;
                            backData = LicenseData.toBytesArray(licenseData);
                            rsaBackData = encryptBackData(backData);
                            break;
                        }

                        Authorization authorization = authorizationRepository.getByGuid(client.getClientGuid());

                        //无软件、定额权限，返回code：hbcNoRight
                        if(!authorizationRepository.hasRecord(client.getClientGuid())){
                            if (service.isKeyExists(configInfo.getRedisAuthInfo(),account)) {
                                byte[] clientInfoBytes = (byte[]) service.get(configInfo.getRedisAuthInfo(),account);
                                ClientInfo clientInfo = (ClientInfo) SerializeUtils.deSerialize(clientInfoBytes);
                                clientInfo.setAuthorization(authorization);
                                service.put(configInfo.getRedisAuthInfo(),account, SerializeUtils.serialize(clientInfo), -1);
                            }
                            licenseData.code = PacketData.HeartBeatCode.hbcNoRight;
                            backData = LicenseData.toBytesArray(licenseData);
                             rsaBackData = encryptBackData(backData);
                            String activity = "登录失败，无软件权限。";
                            addActivities(client.getClientGuid(),mac,ipUrl,activity);
                            break;
                        }


                        //账号是否过期
                        if (validity(authorization)){
                            licenseData.code = PacketData.HeartBeatCode.hbcExpired;
                            backData =LicenseData.toBytesArray(licenseData);
                             rsaBackData = encryptBackData(backData);
                            String activity = "登录失败，账号过期。";
                            addActivities(client.getClientGuid(),mac,ipUrl,activity);
                            break;
                        }




                        ClientInfo clientInfo = new ClientInfo();
                        //是否有登录记录
                        if (service.isKeyExists(configInfo.getRedisAuthInfo(),account)){
                            byte[] clientInfoBytes = (byte[]) service.get(configInfo.getRedisAuthInfo(),account);
                             clientInfo = (ClientInfo) SerializeUtils.deSerialize(clientInfoBytes);
                             clientInfo.setAuthorization(authorization);
                        }else {
                            clientInfo.setAuthorization(authorization);
                            addLoginRecord(account,mac,ipUrl,clientInfo);
                            licenseData = successData(clientInfo,client,authorization,licenseData);
                            backData = LicenseData.toBytesArray(licenseData);
                            rsaBackData = encryptBackData(backData);
                            String activity = "登录成功。";
                            addActivities(client.getClientGuid(),mac,ipUrl,activity);
                            break;
                        }


                        //判断登录列表中是否存在该登陆点
                        if (clientInfo.getAccountInfoList().size()!=0){
                            List<AccountInfo> accountInfoList = clientInfo.getAccountInfoList();
                            boolean online = false;
                            for (AccountInfo accountInfo : accountInfoList){
                                if (accountInfo.getMac().equals(mac)){
                                    accountInfo.setTime(new Date());
                                    accountInfo.setUrl(ipUrl);
                                    online = true;
                                }
                            }
                            if (online){
                                service.put(configInfo.getRedisAuthInfo(),account,SerializeUtils.serialize(clientInfo),-1);
                                licenseData = successData(clientInfo,client,authorization,licenseData);
                                backData = LicenseData.toBytesArray(licenseData);
                                 rsaBackData = encryptBackData(backData);
                                String activity = "登录成功。";
                                addActivities(client.getClientGuid(),mac,ipUrl,activity);
                                break;
                            }

                        }

                        List<OfflineInfo> offlineInfoList = clientInfo.getOfflineInfoList();
                        List<AccountInfo> accountInfoList = clientInfo.getAccountInfoList();
                        //该账号存在离线的登录点，判断当前mac地址是否是离线登陆点
                        if(clientInfo.getOfflineInfoList().size()!=0){
                           boolean offline = false;
                            boolean hasSpace = false;
                            for(int i =0;i<offlineInfoList.size();i++){
                                if (offlineInfoList.get(i).getMac().equals(mac)){
                                    offlineInfoList.remove(offlineInfoList.get(i));
                                    AccountInfo accountInfo = new AccountInfo();
                                    accountInfo.setTime(new Date());
                                    accountInfo.setMac(mac);
                                    accountInfo.setUrl(ipUrl);
                                    accountInfoList.add(accountInfo);
                                    offline = true;
                                }
                            }
                            //该mac地址是离线登录点，删掉该地址的离线记录，并正常登录
                            if (offline){
                                service.put(configInfo.getRedisAuthInfo(),account,SerializeUtils.serialize(clientInfo),-1);
                                licenseData = successData(clientInfo,client,authorization,licenseData);
                                backData = LicenseData.toBytesArray(licenseData);
                                rsaBackData = encryptBackData(backData);
                                String activity = "登录成功。";
                                addActivities(client.getClientGuid(),mac,ipUrl,activity);
                                break;
                            }
                            //该地址不是离线点，判断在线量是否满额？
                            if (clientInfo.getAccountInfoList().size()+clientInfo.getOfflineInfoList().size()<authorization.getPointsForlogin()){
                                addLoginRecord(account,mac,ipUrl,clientInfo);
                                licenseData = successData(clientInfo,client,authorization,licenseData);
                                backData = LicenseData.toBytesArray(licenseData);
                                 rsaBackData = encryptBackData(backData);
                                String activity = "登录成功。";
                                addActivities(client.getClientGuid(),mac,ipUrl,activity);
                                break;
                            }else {
                                hasSpace = hasSpace(accountInfoList,authorization,offlineInfoList);
                                Date now = new Date();
                                for (int i=0;i<offlineInfoList.size();i++){
                                    //账号达到最大离线时间，删除、标识有空位
                                    if (now.getTime()-offlineInfoList.get(i).getTime().getTime()>=authorization.getOfflineTime()*60*1000){
                                        offlineInfoList.remove(offlineInfoList.get(i));
                                        hasSpace=true;
                                    }
                                }
                                if (hasSpace){
                                    addLoginRecord(account,mac,ipUrl,clientInfo);
                                    licenseData = successData(clientInfo,client,authorization,licenseData);
                                    backData =LicenseData.toBytesArray(licenseData);
                                     rsaBackData = encryptBackData(backData);
                                    String activity = "登录成功。";
                                    addActivities(client.getClientGuid(),mac,ipUrl,activity);
                                    break;
                                }else {
                                    service.put(configInfo.getRedisAuthInfo(),account,SerializeUtils.serialize(clientInfo),-1);
                                    licenseData.code = PacketData.HeartBeatCode.hbcReachLimit;
                                    backData = LicenseData.toBytesArray(licenseData);
                                   rsaBackData = encryptBackData(backData);
                                    String activity = "登录失败，登陆节点达到上限";
                                    addActivities(client.getClientGuid(),mac,ipUrl,activity);
                                    break;
                                }
                            }

                        }else {
                            //该账号在线量是否小于总的登录点数
                            if (clientInfo.getAccountInfoList().size()<authorization.getPointsForlogin()){
                                addLoginRecord(account,mac,ipUrl,clientInfo);
                                licenseData = successData(clientInfo,client,authorization,licenseData);
                                backData = LicenseData.toBytesArray(licenseData);
                                 rsaBackData = encryptBackData(backData);
                                String activity = "登录成功。";
                                addActivities(client.getClientGuid(),mac,ipUrl,activity);
                                break;
                            }else {
                                if (hasSpace(accountInfoList,authorization,offlineInfoList)){
                                    addLoginRecord(account,mac,ipUrl,clientInfo);
                                    licenseData = successData(clientInfo,client,authorization,licenseData);
                                    backData = LicenseData.toBytesArray(licenseData);
                                     rsaBackData = encryptBackData(backData);
                                    String activity = "登录成功。";
                                    addActivities(client.getClientGuid(),mac,ipUrl,activity);
                                    break;
                                }else {
                                    service.put(configInfo.getRedisAuthInfo(),account,SerializeUtils.serialize(clientInfo),-1);
                                    licenseData.code = PacketData.HeartBeatCode.hbcReachLimit;
                                    backData = LicenseData.toBytesArray(licenseData);
                                     rsaBackData = encryptBackData(backData);
                                    String activity = "登录失败，登陆节点达到上限";
                                    addActivities(client.getClientGuid(),mac,ipUrl,activity);
                                    break;
                                }
                            }
                        }



                    case dptHeartBeatData:
                        PumpData pumpData = PumpData.fromBytesArray(packet.getData());
                        PacketData packetData = new PacketData();
                        if (pumpData==null){
                            packetData.nToken = 0;
                            backData = PacketData.toBytesArray(packetData);
                            break;
                        }
                        packetData.nToken = pumpData.nToken;
                        String szName = new String(StrUtil.arrayTrim(pumpData.szName));
                        String ip = packet.getAddress().toString()+":"+packet.getPort();
                        ClientInfo clientInfos;
                        if (service.isKeyExists(configInfo.getRedisAuthInfo(),szName)){
                            byte[] clientInfoBytes = (byte[]) service.get(configInfo.getRedisAuthInfo(),szName);
                            clientInfos = (ClientInfo) SerializeUtils.deSerialize(clientInfoBytes);
                            boolean hasLogin = false;
                            //是否在线
                            List<AccountInfo> accountInfoList1 = clientInfos.getAccountInfoList();
                            for (AccountInfo accountInfo :accountInfoList1){
                                if (accountInfo.getUrl().equals(ip)){
                                    accountInfo.setTime(new Date());
                                    hasLogin = true;
                                    break;
                                }
                            }
                            if (hasLogin){
                                service.put(configInfo.getRedisAuthInfo(),szName,SerializeUtils.serialize(clientInfos),-1);
                            }else {
                                packetData.code = PacketData.HeartBeatCode.hbcNotLogin;
                                backData = PacketData.toBytesArray(packetData);
                                break;
                            }
                            //是否授权
                            if (clientInfos.getAuthorization()==null){
                                if(!authorizationRepository.whetherAuthByName(szName)){
                                    packetData.code = PacketData.HeartBeatCode.hbcNoRight;
                                    backData = PacketData.toBytesArray(packetData);
                                    break;
                                }
                            }

                            //是否过期
                            if (validity(clientInfos.getAuthorization())){
                                packetData.code = PacketData.HeartBeatCode.hbcExpired;
                                backData = PacketData.toBytesArray(packetData);
                                break;
                            }

                            //心跳正常
                            packetData.code = PacketData.HeartBeatCode.hbcSuccess;
                            int validTime = clientInfos.getAuthorization().getValidityTime();
                            if (validTime==-1){
                                packetData.validTime=-1;
                            }else {
                                packetData.validTime= StrUtil.surplusTime(clientInfos.getAuthorization().getTime(),clientInfos.getAuthorization().getValidityTime());
                            }
                                packetData.nConnectionCnt= StrUtil.pointsToNum(clientInfos.getAccountInfoList().size(),clientInfos.getAuthorization().getPointsForlogin());
                            backData = PacketData.toBytesArray(packetData);
                            break;
                        }else {
                            packetData.code = PacketData.HeartBeatCode.hbcNotLogin;
                            backData = PacketData.toBytesArray(packetData);
                            break;
                        }


                    case dptLogoutData:
                        LogoutData logoutData = LogoutData.fromBytesArray(packet.getData());
                        String username = new String(StrUtil.arrayTrim(logoutData.name));
                        int sign = logoutData.sign;
                        Client thisClient = clientRepository.getByAccount(username);
                        String url = packet.getAddress().toString()+":"+packet.getPort();
                        ClientInfo theClientInfo;
                            if (service.isKeyExists(configInfo.getRedisAuthInfo(),username)) {
                                byte[] clientInfoBytes = (byte[]) service.get(configInfo.getRedisAuthInfo(),username);
                                theClientInfo = (ClientInfo) SerializeUtils.deSerialize(clientInfoBytes);
                                if (sign==0&&theClientInfo.getAuthorization()!=null&&theClientInfo.getAuthorization().getOfflineTime()>0){
                                    List<OfflineInfo> offlineInfoLists = theClientInfo.getOfflineInfoList();
                                    List<AccountInfo> accountInfoLists = theClientInfo.getAccountInfoList();
                                    for (int i = 0;i<accountInfoLists.size();i++){
                                        if (accountInfoLists.get(i).getUrl().equals(url)){
                                            OfflineInfo offlineInfo = new OfflineInfo();
                                            offlineInfo.setUrl(accountInfoLists.get(i).getUrl());
                                            offlineInfo.setMac(accountInfoLists.get(i).getMac());
                                            offlineInfo.setTime(new Date());
                                            accountInfoLists.remove(accountInfoLists.get(i));
                                            offlineInfoLists.add(offlineInfo);
                                        }
                                    }
                                }else {
                                    List<AccountInfo> accountInfoList1 = theClientInfo.getAccountInfoList();
                                    for (int i = 0; i < accountInfoList1.size(); i++) {
                                        if (accountInfoList1.get(i).getUrl().equals(url)) {
                                            accountInfoList1.remove(accountInfoList1.get(i));
                                        }
                                    }
                                }

                                service.put(configInfo.getRedisAuthInfo(),username, SerializeUtils.serialize(theClientInfo), -1);
                                String activity = "退出登录";
                                addActivities(thisClient.getClientGuid(),"/",url,activity);
                            }


                        break;

                    default:
                        break;
                }
                if (rsaBackData != null){
                    backData = rsaBackData;
                }
                if (backData != null){
                    InetAddress address = packet.getAddress();
                    int port = packet.getPort();

                    DatagramPacket backDatagramPacket = new DatagramPacket(backData, backData.length, address, port);

                    // 给客户端发送数据
                    socket.send(backDatagramPacket);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 添加登录记录
     * @param account
     * @param mac
     * @param url
     * @param clientInfo
     */
    private void addLoginRecord(String account,String mac,String url,ClientInfo clientInfo){
        AccountInfo accountInfo = new AccountInfo();
        accountInfo.setMac(mac);
        accountInfo.setTime(new Date());
        accountInfo.setUrl(url);
        clientInfo.getAccountInfoList().add(accountInfo);
        service.put(configInfo.getRedisAuthInfo(),account,SerializeUtils.serialize(clientInfo),-1);
    }

    /**
     * 登录成功的返回数据
     * @param authorization
     * @param licenseData
     * @return
     * @throws Exception
     */
    private LicenseData successData(ClientInfo clientInfo,Client client, Authorization authorization, LicenseData licenseData) throws Exception {

        licenseData.softID = StrUtil.toIntLongArray((int[])SerializeUtils.deSerialize(RSAUtils.decryptByPublicKey(Base64.decodeBase64(authorization.getAuthApp()),RSAUtils.getPublicKey())),4);
        int[] ints = (int[])SerializeUtils.deSerialize(RSAUtils.decryptByPublicKey(Base64.decodeBase64(authorization.getAuthQuota()),RSAUtils.getPublicKey()));
        licenseData.normID = StrUtil.toIntLongArray((int[])SerializeUtils.deSerialize(RSAUtils.decryptByPublicKey(Base64.decodeBase64(authorization.getAuthQuota()),RSAUtils.getPublicKey())),64);
        if (client.getRoleGuid()!=null){
            licenseData.permission = roleRepository.getPermission(client.getRoleGuid());
        }
        licenseData.realName = StrUtil.toByteLongArray(client.getName().getBytes("utf-8"),16);
        if (authorization.getValidityTime()==-1){
            licenseData.validTime=-1;
        }else {
            licenseData.validTime= StrUtil.surplusTime(authorization.getTime(),authorization.getValidityTime());
        }
        licenseData.nConnectionCnt = StrUtil.pointsToNum(clientInfo.getAccountInfoList().size(),authorization.getPointsForlogin());
        licenseData.code = PacketData.HeartBeatCode.hbcSuccess;
        licenseData.maxTempAuthTime = authorization.getOfflineTime();
        return licenseData;
    }


    /**
     * 判断是否过期
     * @param authorization
     * @return
     */
    private boolean validity(Authorization authorization){
        if (authorization.getValidityTime()==-1){
            return false;
        }
        Date now = new Date();

        return now.getTime() - authorization.getTime().getTime() >= StrUtil.getMilliseconds(authorization.getValidityTime());
    }


    /**
     * 检测并处理在线列表中的超时账号
     * @param accountInfoList
     * @param authorization
     * @param offlineInfoList
     * @return
     */
    private boolean hasSpace(List<AccountInfo> accountInfoList , Authorization authorization ,List<OfflineInfo> offlineInfoList){
        boolean hasSpace = false ;
        Date now = new Date();
        for (int i=0;i<accountInfoList.size();i++){
            //账号超时40秒
            if(now.getTime()-accountInfoList.get(i).getTime().getTime()>40*1000 ){
                //有离线功能的，移至离线列表；没有则直接删除，并标识有空位
                if (authorization.getOfflineTime()>0){
                    OfflineInfo offlineInfo = new OfflineInfo();
                    offlineInfo.setUrl(accountInfoList.get(i).getUrl());
                    offlineInfo.setMac(accountInfoList.get(i).getMac());
                    offlineInfo.setTime(new Date());
                    offlineInfoList.add(offlineInfo);
                    accountInfoList.remove(accountInfoList.get(i));
                }else {
                    accountInfoList.remove(accountInfoList.get(i));
                    hasSpace=true;
                }
            }
        }
        return hasSpace ;
    }

    private void addActivities(String clientGuid,String mac,String ip,String activity){
        Activities activities = new Activities();
        activities.setClientGuid(clientGuid);
        activities.setMac(mac);
        activities.setIp(ip);
        activities.setActivity(activity);
        activities.setTime(new Date());
        activitiesRepository.insertActivities(activities);
    }

    private byte[] encryptBackData(byte[] backData) throws Exception {
        return RSAUtils.encryptByPrivateKey(backData,RSAUtils.getPrivateKey());
    }




}
